{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# Generic PDK\n",
    "\n",
    "GDSFactory includes a generic Process Design Kit PDK, which is a library of components associated to a generic foundry process `gdsfactory.generic_tech`.\n",
    "See components available in the [generic component library](https://gdsfactory.github.io/gdsfactory/components.html) that you can customize or adapt to create your own.\n",
    "\n",
    "The generic process including layer numbers is based on the book \"Silicon Photonics Design: From Devices to Systems Lukas Chrostowski, Michael Hochberg\".\n",
    "You can learn more about process design kits (PDKs) [in this tutorial](https://gdsfactory.github.io/gdsfactory/notebooks/08_pdk.html)\n",
    "\n",
    "## LayerMap\n",
    "\n",
    "A layer map maps layer names to an integer numbers pair (GDSlayer, GDSpurpose)\n",
    "\n",
    "Each foundry uses different GDS layer numbers for different process steps.\n",
    "\n",
    "| GDS (layer, purpose) | layer_name | Description                                                 |\n",
    "| -------------------- | ---------- | ----------------------------------------------------------- |\n",
    "| 1 , 0                | WG         | 220 nm Silicon core                                         |\n",
    "| 2 , 0                | SLAB150    | 150nm Silicon slab (70nm shallow Etch for grating couplers) |\n",
    "| 3 , 0                | SLAB90     | 90nm Silicon slab (for modulators)                          |\n",
    "| 4, 0                 | DEEPTRENCH | Deep trench                                                 |\n",
    "| 47, 0                | MH         | heater                                                      |\n",
    "| 41, 0                | M1         | metal 1                                                     |\n",
    "| 45, 0                | M2         | metal 2                                                     |\n",
    "| 40, 0                | VIAC       | VIAC to contact Ge, NPP or PPP                              |\n",
    "| 44, 0                | VIA1       | VIA1                                                        |\n",
    "| 46, 0                | PADOPEN    | Bond pad opening                                            |\n",
    "| 51, 0                | UNDERCUT   | Undercut                                                    |\n",
    "| 66, 0                | TEXT       | Text markup                                                 |\n",
    "| 64, 0                | FLOORPLAN  | Mask floorplan                                              |\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "from IPython.display import Code\n",
    "\n",
    "import gdsfactory as gf\n",
    "from gdsfactory.config import PATH\n",
    "from gdsfactory.generic_tech import LAYER_STACK, get_generic_pdk\n",
    "from gdsfactory.generic_tech.get_klayout_pyxs import get_klayout_pyxs\n",
    "from gdsfactory.technology import LayerLevel, LayerMap, LayerStack, LayerViews\n",
    "from gdsfactory.typings import Layer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [],
   "source": [
    "class LAYER(LayerMap):\n",
    "    \"\"\"Generic layermap based on book.\n",
    "\n",
    "    Lukas Chrostowski, Michael Hochberg, \"Silicon Photonics Design\",\n",
    "    Cambridge University Press 2015, page 353\n",
    "    You will need to create a new LayerMap with your specific foundry layers.\n",
    "    \"\"\"\n",
    "\n",
    "    WAFER: Layer = (999, 0)\n",
    "\n",
    "    WG: Layer = (1, 0)\n",
    "    WGCLAD: Layer = (111, 0)\n",
    "    SLAB150: Layer = (2, 0)\n",
    "    SLAB90: Layer = (3, 0)\n",
    "    DEEPTRENCH: Layer = (4, 0)\n",
    "    GE: Layer = (5, 0)\n",
    "    UNDERCUT: Layer = (6, 0)\n",
    "    WGN: Layer = (34, 0)\n",
    "    WGN_CLAD: Layer = (36, 0)\n",
    "\n",
    "    N: Layer = (20, 0)\n",
    "    NP: Layer = (22, 0)\n",
    "    NPP: Layer = (24, 0)\n",
    "    P: Layer = (21, 0)\n",
    "    PP: Layer = (23, 0)\n",
    "    PPP: Layer = (25, 0)\n",
    "    GEN: Layer = (26, 0)\n",
    "    GEP: Layer = (27, 0)\n",
    "\n",
    "    HEATER: Layer = (47, 0)\n",
    "    M1: Layer = (41, 0)\n",
    "    M2: Layer = (45, 0)\n",
    "    M3: Layer = (49, 0)\n",
    "    VIAC: Layer = (40, 0)\n",
    "    VIA1: Layer = (44, 0)\n",
    "    VIA2: Layer = (43, 0)\n",
    "    PADOPEN: Layer = (46, 0)\n",
    "\n",
    "    DICING: Layer = (100, 0)\n",
    "    NO_TILE_SI: Layer = (71, 0)\n",
    "    PADDING: Layer = (67, 0)\n",
    "    DEVREC: Layer = (68, 0)\n",
    "    FLOORPLAN: Layer = (64, 0)\n",
    "    TEXT: Layer = (66, 0)\n",
    "    PORT: Layer = (1, 10)\n",
    "    PORTE: Layer = (1, 11)\n",
    "    PORTH: Layer = (70, 0)\n",
    "    SHOW_PORTS: Layer = (1, 12)\n",
    "    LABEL_SETTINGS: Layer = (202, 0)\n",
    "    DRC_MARKER: Layer = (205, 0)\n",
    "    LABEL_INSTANCE: Layer = (206, 0)\n",
    "\n",
    "    SOURCE: Layer = (110, 0)\n",
    "    MONITOR: Layer = (101, 0)\n",
    "\n",
    "\n",
    "LAYER.WG"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3",
   "metadata": {},
   "outputs": [],
   "source": [
    "layer_wg = (1, 0)\n",
    "print(layer_wg)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4",
   "metadata": {},
   "source": [
    "### Extract layers\n",
    "\n",
    "You can also extract layers using the `extract` function. This function returns a new flattened component that contains the extracted layers.\n",
    "A flat component does not have references, and all the polygons are absorbed into the top cell."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# A PDK is a collection of pre-defined components, layers, and design rules for a specific manufacturing process. \n",
    "# This code loads a generic, open-source PDK and sets it as the active one for the current gdsfactory session.\n",
    "PDK = get_generic_pdk()\n",
    "PDK.activate()\n",
    "\n",
    "# This line retrieves the layer_views object from the PDK. \n",
    "# This object contains information on how each layer in the PDK should be displayed, including its color, transparency, and name.\n",
    "LAYER_VIEWS = PDK.layer_views\n",
    "\n",
    "# This is a helper function that creates a gdsfactory component specifically designed to visualize the entire layer set.\n",
    "# It draws a series of labeled, colored boxes, with each box representing a different layer from the PDK.\n",
    "c = LAYER_VIEWS.preview_layerset()\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "extract = c.extract(layers=((41, 0), (40, 0)))\n",
    "extract.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7",
   "metadata": {},
   "source": [
    "### Remove layers\n",
    "\n",
    "You can remove layers using the `remove_layers()` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# .extract(...): This is a method that acts like a filter.\n",
    "# It goes through the component c and pulls out only the shapes that are on the layers specified in the layers argument.\n",
    "# layers=((41, 0), (40, 0)): This tuple specifies which layers to keep. In this case, it will extract all geometry from layer (41, 0) and layer (40, 0).\n",
    "# The result is a brand new component, assigned to the extract variable, that contains a copy of only the desired shapes.\n",
    "removed = extract.remove_layers(layers=((40, 0),))\n",
    "removed.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9",
   "metadata": {},
   "source": [
    "### Remap layers\n",
    "\n",
    "You can remap (change the polygons from one layer to another layer) using the `remap_layer`, which will return a new `Component`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.components.rectangle(layer=(2, 0))\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = c.copy() # This line creates a duplicate of the original component c. This is good practice to ensure that the original component remains unchanged.\n",
    "\n",
    "# The remap_layers method goes through the component and reassigns layers based on the dictionary provided.\n",
    "# The dictionary {(2, 0): (34, 0)} defines the mapping rule: \"find all shapes on layer (2, 0) and move them to layer (34, 0).\"\n",
    "# The result is a new component, assigned to the remap variable, where the layer change has been applied.\n",
    "remap = c.remap_layers({(2, 0): (34, 0)})\n",
    "remap.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12",
   "metadata": {},
   "source": [
    "## LayerViews\n",
    "\n",
    "Klayout shows each GDS layer with a color, style and transparency.\n",
    "\n",
    "You can define your layerViews in a klayout Layer Properties file `layers.lyp` or in `YAML` format.\n",
    "\n",
    "We recommend using YAML and then generate the lyp in klayout, as YAML is easier to modify than XML."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13",
   "metadata": {},
   "outputs": [],
   "source": [
    "Code(filename=PATH.klayout_yaml)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14",
   "metadata": {},
   "source": [
    "Once you modify the `YAML` file you can easily write it to klayout layer properties `lyp` or the other way around.\n",
    "\n",
    "```\n",
    "YAML <---> LYP\n",
    "```\n",
    "\n",
    "The functions `LayerView.to_lyp(filepath)` and `LayerView.to_yaml(filepath)` allow you to convert from each other.\n",
    "\n",
    "LYP is based on XML so it's much easier to make changes and maintain the equivalent YAML file."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15",
   "metadata": {},
   "source": [
    "### YAML -> LYP\n",
    "\n",
    "You can easily convert from YAML into Klayout Layer Properties."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16",
   "metadata": {},
   "outputs": [],
   "source": [
    "# A KLayout Layer Properties (.lyp) file stores all the visual settings for your layers, such as color, fill pattern, name, and visibility.\n",
    "# This line reads the settings from the .lyp file specified by PATH.klayout_lyp and loads them into a LayerViews object in memory.\n",
    "LAYER_VIEWS = LayerViews(filepath=PATH.klayout_lyp)\n",
    "\n",
    "# This line takes the LayerViews object, which now holds all the settings from the original file, \n",
    "# and writes them out to a new file named klayout_layers.lyp inside the extra directory.\n",
    "LAYER_VIEWS.to_lyp(\"extra/klayout_layers.lyp\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17",
   "metadata": {},
   "source": [
    "### LYP -> YAML\n",
    "\n",
    "Sometimes you start from an LYP XML file. We recommend converting to YAML and using the YAML as the layer views source of truth.\n",
    "\n",
    "Layers in YAML are easier to read and modify than doing it in klayout XML format."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "LAYER_VIEWS = LayerViews(filepath=PATH.klayout_lyp)\n",
    "\n",
    "# This line takes the LayerViews object and writes the settings to a new file named layers.yaml in YAML format.\n",
    "# YAML is a text-based format that is easy for humans to read and edit, and it's also easily parsed by software.\n",
    "LAYER_VIEWS.to_yaml(\"extra/layers.yaml\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19",
   "metadata": {},
   "source": [
    "### Preview layerset\n",
    "\n",
    "You can preview all the layers defined in your `LayerViews`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = LAYER_VIEWS.preview_layerset()\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21",
   "metadata": {},
   "source": [
    "By default the generic PDK has some layers that are not visible and therefore are not shown."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22",
   "metadata": {},
   "outputs": [],
   "source": [
    "c_wg_clad = c.extract(layers=[(1, 0)])\n",
    "c_wg_clad.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23",
   "metadata": {},
   "outputs": [],
   "source": [
    "# .layer_views: This is an attribute of the LAYER_VIEWS object that acts like a dictionary,\n",
    "#  where the keys are the layer names (e.g., \"WGCLAD\", \"SI\") and the values are the display settings for each layer.\n",
    "# [\"WGCLAD\"]: This is standard dictionary syntax to look up and return the settings associated with the key \"WGCLAD\".\n",
    "LAYER_VIEWS.layer_views[\"WGCLAD\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24",
   "metadata": {},
   "outputs": [],
   "source": [
    "# .visible: This is a boolean attribute of the layer view object that returns True if the layer is set to be visible in the KLayout viewer,\n",
    "# and False if it is hidden.\n",
    "LAYER_VIEWS.layer_views[\"WGCLAD\"].visible"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25",
   "metadata": {},
   "source": [
    "You can make it visible"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LAYER_VIEWS.layer_views[\"WGCLAD\"].visible = True\n",
    "# This line accesses the display settings for the layer named \"WGCLAD\" and sets its visible property to True.\n",
    "# This would make the layer visible in the KLayout viewer when these layer properties are loaded.\n",
    "LAYER_VIEWS.layer_views[\"WGCLAD\"].visible = True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "27",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LAYER_VIEWS.layer_views[\"WGCLAD\"].visible\n",
    "# In an interactive session, this line would retrieve and display the new visibility status, which would be True.\n",
    "LAYER_VIEWS.layer_views[\"WGCLAD\"].visible"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28",
   "metadata": {},
   "outputs": [],
   "source": [
    "c_ge = c.extract(layers=[(5, 0)])\n",
    "c_ge.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29",
   "metadata": {},
   "source": [
    "## LayerStack\n",
    "\n",
    "Each layer also includes the information of thickness and position of each layer after fabrication.\n",
    "\n",
    "This LayerStack can be used for creating a 3D model with `Component.to_3d` or running simulations.\n",
    "\n",
    "A GDS has different layers to describe the different fabrication process steps. And each grown layer needs thickness information and a z-position in the stack.\n",
    "\n",
    "![layer stack](https://i.imgur.com/GUb1Kav.png)\n",
    "\n",
    "Let us define the layer stack for the generic layers in the generic_technology."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "30",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "# This imports a predefined layer map named LAYER.\n",
    "# This is a convenient object that contains ready-to-use definitions for common layers in a generic fabrication process,\n",
    "# such as LAYER.WG for waveguides or LAYER.SLAB90 for slabs.\n",
    "from gdsfactory.generic_tech.layer_map import LAYER\n",
    "\n",
    "# This imports the LogicalLayer class. This is a more structured way to define a layer,\n",
    "#  allowing you to associate not just the GDSII layer and purpose numbers, but also other metadata like the material or the name of the layer.\n",
    "from gdsfactory.technology import LogicalLayer\n",
    "\n",
    "# This line sets up a convenient conversion factor. Since gdsfactory and most photonics tools work in micrometers (µm),\n",
    "# this variable allows you to define thicknesses in nanometers (nm) and easily convert them to µm (1 nm = 0.001 µm).\n",
    "nm = 1e-3\n",
    "\n",
    "# This defines the total thickness of the main silicon waveguide layer as 220 nm (0.22 µm).\n",
    "# This is a very common standard for Silicon-on-Insulator (SOI) wafers used in photonics.\n",
    "# A deep etch is a microfabrication process characterized by its anisotropy, meaning it etches downwards much faster than sideways.\n",
    "# A shallow etch is a microfabrication process that removes a thin, precisely controlled layer of material from the surface of a wafer,\n",
    "# without cutting deep into it. It is the opposite of a deep etch.\n",
    "thickness_wg = 220 * nm\n",
    "thickness_slab_deep_etch = 90 * nm # thickness_slab_deep_etch = 90 * nm: Defines the slab thickness after a deep etch (a 90 nm slab remains).\n",
    "thickness_slab_shallow_etch = 150 * nm # thickness_slab_shallow_etch = 150 * nm: Defines the slab thickness after a shallow etch (a 150 nm slab remains).\n",
    "\n",
    "# This variable defines the sidewall angle of the waveguide in degrees.\n",
    "# This parameter allows you to model the actual slope of the waveguide's sides. A value of 0 represents an ideal, perfectly vertical 90-degree etch.\n",
    "sidewall_angle_wg = 0\n",
    "layer_core = LogicalLayer(layer=LAYER.WG) #  Represents the main waveguide layer.\n",
    "layer_shallow_etch = LogicalLayer(layer=LAYER.SHALLOW_ETCH) # Represents the areas to be shallowly etched.\n",
    "layer_deep_etch = LogicalLayer(layer=LAYER.DEEP_ETCH) # Represents the areas to be deeply etched.\n",
    "\n",
    "\n",
    "layers = {\n",
    "    \"core\": LayerLevel(\n",
    "\n",
    "        # This defines the final shape of the core.\n",
    "        # It starts with the full waveguide shape (layer_core) and then \"cuts away\" the areas that are designated for deep etching and shallow etching.\n",
    "        # The result is the geometry for the unetched, full-height part of the waveguide.\n",
    "        layer=layer_core - layer_deep_etch - layer_shallow_etch,\n",
    "        thickness=thickness_wg,\n",
    "        zmin=0.0, # The vertical position where this layer starts\n",
    "        material=\"si\", # The material used is silicon\n",
    "        mesh_order=2, # A priority setting used by simulation software when generating a mesh; higher numbers are processed first.\n",
    "        sidewall_angle=sidewall_angle_wg,\n",
    "        width_to_z=0.5, # For every 1 µm of height, the top surface is narrowed by 0.5 µm on each side. This creates a specific, sloped sidewall.\n",
    "        derived_layer=layer_core,\n",
    "    ),\n",
    "    \"shallow_etch\": LayerLevel(\n",
    "        layer=LogicalLayer(layer=LAYER.SHALLOW_ETCH),\n",
    "        thickness=thickness_wg - thickness_slab_shallow_etch,\n",
    "        zmin=0.0,\n",
    "        material=\"si\",\n",
    "        mesh_order=1,\n",
    "        derived_layer=LogicalLayer(layer=LAYER.SLAB150),\n",
    "    ),\n",
    "    \"deep_etch\": LayerLevel(\n",
    "        layer=LogicalLayer(layer=LAYER.DEEP_ETCH),\n",
    "        thickness=thickness_wg - thickness_slab_deep_etch,\n",
    "        zmin=0.0,\n",
    "        material=\"si\",\n",
    "        mesh_order=1,\n",
    "        derived_layer=LogicalLayer(layer=LAYER.SLAB90),\n",
    "    ),\n",
    "    \"slab150\": LayerLevel(\n",
    "        layer=LogicalLayer(layer=LAYER.SLAB150),\n",
    "        thickness=150e-3,\n",
    "        zmin=0,\n",
    "        material=\"si\",\n",
    "        mesh_order=3,\n",
    "    ),\n",
    "    \"slab90\": LayerLevel(\n",
    "        layer=LogicalLayer(layer=LAYER.SLAB90),\n",
    "        thickness=thickness_slab_deep_etch,\n",
    "        zmin=0.0,\n",
    "        material=\"si\",\n",
    "        mesh_order=2,\n",
    "    ),\n",
    "}\n",
    "\n",
    "\n",
    "layer_stack = LayerStack(layers=layers)\n",
    "\n",
    "c = gf.c.grating_coupler_elliptical_trenches()\n",
    "s = c.to_3d(layer_stack=layer_stack)\n",
    "s.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "31",
   "metadata": {},
   "outputs": [],
   "source": [
    "from gdsfactory.generic_tech.layer_stack import get_layer_stack\n",
    "\n",
    "layer_stack220 = get_layer_stack()\n",
    "\n",
    "# Rib Waveguide: Unlike a strip waveguide, a rib waveguide has a central core on top of a thinner \"slab\" of the same material.\n",
    "# This design offers a good balance between light confinement and lower propagation losses.\n",
    "# On either side of the rib core, there are doped P and N regions. These act as a resistive heater.\n",
    "# When a voltage is applied, current flows through these regions, generating heat to change the waveguide's refractive index.\n",
    "# Length: The length=100 parameter sets the total length of the component to 100 µm.\n",
    "c = gf.c.straight_heater_doped_rib(length=100)\n",
    "c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32",
   "metadata": {},
   "outputs": [],
   "source": [
    "scene = c.to_3d(layer_stack=layer_stack220)\n",
    "scene.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "33",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.components.straight_heater_metal(length=90)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34",
   "metadata": {},
   "outputs": [],
   "source": [
    "scene = c.to_3d(layer_stack=layer_stack220)\n",
    "scene.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "35",
   "metadata": {},
   "outputs": [],
   "source": [
    "# The taper_strip_to_ridge_trenches() component acts as a taper for the waveguide core and adds trenches on either side that define the rib structure.\n",
    "# The trenches are etched into the silicon, leaving behind the central rib and the surrounding slab.\n",
    "c = gf.components.taper_strip_to_ridge_trenches()\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36",
   "metadata": {},
   "outputs": [],
   "source": [
    "scene = c.to_3d(layer_stack=layer_stack220)\n",
    "scene.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "37",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let us assume we have 900nm silicon instead of 220nm, you will see a much thicker waveguide under the metal heater.\n",
    "layer_stack900 = get_layer_stack(thickness_wg=900 * nm)\n",
    "scene = c.to_3d(layer_stack=layer_stack900)\n",
    "scene.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "c = gf.components.grating_coupler_elliptical_trenches()\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39",
   "metadata": {},
   "outputs": [],
   "source": [
    "scene = c.to_3d()\n",
    "scene.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40",
   "metadata": {},
   "source": [
    "### 3D rendering\n",
    "\n",
    "To render components in 3D you will need to define two things:\n",
    "\n",
    "1. LayerStack: for each layer contains thickness of each material and z position\n",
    "2. LayerViews: for each layer contains view (color, pattern, opacity). You can load it with `gf.technology.LayerView.load_lyp()`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "41",
   "metadata": {},
   "outputs": [],
   "source": [
    "heater = gf.components.straight_heater_metal(length=90)\n",
    "heater.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42",
   "metadata": {},
   "outputs": [],
   "source": [
    "scene = heater.to_3d()\n",
    "scene.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43",
   "metadata": {},
   "source": [
    "### Klayout 2.5D view\n",
    "\n",
    "From the `LayerStack` you can generate the KLayout 2.5D view script."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44",
   "metadata": {},
   "outputs": [],
   "source": [
    "LAYER_STACK.get_klayout_3d_script()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "45",
   "metadata": {},
   "source": [
    "Then you go to Tools → Manage Technologies\n",
    "\n",
    "\n",
    "![klayout](https://i.imgur.com/KCcMRBO.png)\n",
    "\n",
    "and paste the 2.5D view script\n",
    "\n",
    "![paste](https://i.imgur.com/CoTythB.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46",
   "metadata": {},
   "source": [
    "### Klayout cross-section\n",
    "\n",
    "You can also install the [KLayout cross-section plugin](https://gdsfactory.github.io/klayout_pyxs/README.html)\n",
    "\n",
    "![xsection](https://i.imgur.com/xpPS8fM.png)\n",
    "\n",
    "This is not integrated with the LayerStack but you can customize the script in `gdsfactory.generic_tech.get_klayout_pyxs` for your technology."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "47",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "outputs": [],
   "source": [
    "nm = 1e-3\n",
    "if __name__ == \"__main__\":\n",
    "\n",
    "    # t_...: Defines the thickness of different material layers (e.g., t_si=220*nm for the silicon device layer, t_m1=0.5 for the first metal layer).\n",
    "    # h_etch...: Defines the height or depth of different etching steps.\n",
    "    # gap_...: Defines the vertical gap or spacing between layers (e.g., gap_m1_m2=0.6 for the space between metal 1 and metal 2).\n",
    "    # The layer_ function help with layer assignments:\n",
    "    # These parameters map the different logical parts of the design to specific GDSII layer numbers (e.g., LAYER.WG, LAYER.M1).\n",
    "    # Waveguide Layers: layer_wg, layer_rib, layer_nitride.\n",
    "    # Doping Layers: layer_n, layer_p, layer_npp, etc., for creating electronic junctions.\n",
    "    # Germanium Layers: layer_Ge, layer_GePPp for photodetectors.\n",
    "    # Metal and Via Layers: layer_m1, layer_via1, layer_m2, etc., for electrical routing.\n",
    "    \n",
    "    script = get_klayout_pyxs(\n",
    "        t_box=2.0,\n",
    "        t_slab=110 * nm,\n",
    "        t_si=220 * nm,\n",
    "        t_ge=400 * nm,\n",
    "        t_nitride=400 * nm,\n",
    "        h_etch1=0.07,\n",
    "        h_etch2=0.06,\n",
    "        h_etch3=0.09,\n",
    "        t_clad=0.6,\n",
    "        t_m1=0.5,\n",
    "        t_m2=0.5,\n",
    "        t_m3=2.0,\n",
    "        gap_m1_m2=0.6,\n",
    "        gap_m2_m3=0.3,\n",
    "        t_heater=0.1,\n",
    "        gap_oxide_nitride=0.82,\n",
    "        t_m1_oxide=0.6,\n",
    "        t_m2_oxide=2.0,\n",
    "        t_m3_oxide=0.5,\n",
    "        layer_wg=(1, 0),\n",
    "        layer_fc=(2, 0),\n",
    "        layer_rib=LAYER.SLAB90,\n",
    "        layer_n=LAYER.N,\n",
    "        layer_np=LAYER.NP,\n",
    "        layer_npp=LAYER.NPP,\n",
    "        layer_p=LAYER.P,\n",
    "        layer_pp=LAYER.PP,\n",
    "        layer_ppp=LAYER.PPP,\n",
    "        layer_PDPP=LAYER.GEP,\n",
    "        layer_nitride=LAYER.WGN,\n",
    "        layer_Ge=LAYER.GE,\n",
    "        layer_GePPp=LAYER.GEP,\n",
    "        layer_GeNPP=LAYER.GEN,\n",
    "        layer_viac=LAYER.VIAC,\n",
    "        layer_viac_slot=LAYER.VIAC,\n",
    "        layer_m1=LAYER.M1,\n",
    "        layer_mh=LAYER.HEATER,\n",
    "        layer_via1=LAYER.VIA1,\n",
    "        layer_m2=LAYER.M2,\n",
    "        layer_via2=LAYER.VIA2,\n",
    "        layer_m3=LAYER.M3,\n",
    "        layer_open=LAYER.PADOPEN,\n",
    "    )\n",
    "\n",
    "    # script_path = pathlib.Path(__file__).parent.absolute() / \"xsection_planarized.pyxs\".\n",
    "    # script_path.write_text(script).\n",
    "    print(script)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "48",
   "metadata": {},
   "source": [
    "![xsection generic](https://i.imgur.com/H5Qiygc.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "49",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "source": [
    "## Process\n",
    "\n",
    "The LayerStack uses the GDS layers to generate a representation of the chip after fabrication.\n",
    "\n",
    "The KLayout cross-section module uses the GDS layers to return a geometric approximation of the processed wafer.\n",
    "\n",
    "Sometimes, however, physical process modeling is desired.\n",
    "\n",
    "For these purposes, processes acting on an initial substrate \"wafer stack\" can be defined. The waferstack is a LayerStack representing the initial state of the wafer. The processes take in some combination of GDS layers (which may differ from their use in the resulting LayerStack), some processing parameters, and are then run in sequence.\n",
    "\n",
    "For instance, the early step of the front-end-of-line of the generic process could be approximated as done in `gdsfactory.technology.layer_stack` (the process classes are described in `gdsfactory.technology.processes`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "outputs": [],
   "source": [
    "import gdsfactory.technology.processes as gp\n",
    "\n",
    "\n",
    "def get_process():\n",
    "    \"\"\"Returns generic process to generate LayerStack.\n",
    "\n",
    "    Represents processing steps that will result in the GenericLayerStack, starting from the waferstack LayerStack.\n",
    "\n",
    "    based on paper https://www.degruyter.com/document/doi/10.1515/nanoph-2013-0034/html\n",
    "    \"\"\"\n",
    "    return (\n",
    "        \n",
    "        # The first, deeper etch:\n",
    "        gp.Etch(\n",
    "            name=\"strip_etch\",\n",
    "            layer=(1, 0),\n",
    "            positive_tone=False, # Specifies a negative tone process, the areas covered by the mask are protected, and the surrounding material is etched away.\n",
    "            \n",
    "            # +0.01 signals a slight over-etch.\n",
    "            # A slight overetch is the intentional removal of a small, extra amount of material during the chip fabrication process.\n",
    "            # It is a planned \"safety margin\" to ensure that the etch is complete and uniform across the entire wafer.\n",
    "            depth=0.22 + 0.01, \n",
    "            material=\"core\",\n",
    "\n",
    "            # The resist thickness must be precisely controlled for two main reasons:\n",
    "            # Resolution: If the resist is too thick, it can be difficult to create fine, high-resolution features.\n",
    "            # Durability: The resist must be thick enough to withstand the etching process without being completely eroded\n",
    "            # before the underlying material has been fully etched.\n",
    "            resist_thickness=1.0,\n",
    "        ),\n",
    "        \n",
    "        # The second, shallower etch:\n",
    "        gp.Etch(\n",
    "            name=\"slab_etch\",\n",
    "            layer=LAYER.SLAB90,\n",
    "            layers_diff=[(1, 0)],\n",
    "            depth=0.22 - 0.09, # The etch depth is 0.13 µm. This is a partial etch, leaving a 90 nm slab of silicon (0.22 - 0.13 = 0.09).\n",
    "            material=\"core\",\n",
    "            resist_thickness=1.0,\n",
    "        ),\n",
    "        # See gplugins.process.implant tables for ballpark numbers\n",
    "        # Adjust to your process\n",
    "\n",
    "        # This ImplantPhysical object models the process of implanting ions into a wafer to change its electrical properties (a process called doping).\n",
    "        gp.ImplantPhysical(\n",
    "            name=\"deep_n_implant\",\n",
    "            layer=LAYER.N, # The GDSII layer that acts as the mask. The implant will only occur in the areas defined by the shapes on this layer.\n",
    "            energy=100, # The implantation energy in keV. Higher energy results in the ions being implanted deeper into the material.\n",
    "            \n",
    "            # The type of ion being implanted, in this case, Phosphorus (P).\n",
    "            # Phosphorus has one more valence electron than silicon, so implanting it creates N-type doped silicon.\n",
    "            ion=\"P\", \n",
    "            dose=1e12, # The ion dose, in atoms per cm². This determines the concentration of dopant atoms that are implanted.\n",
    "            resist_thickness=1.0,\n",
    "        ),\n",
    "        gp.ImplantPhysical(\n",
    "            name=\"shallow_n_implant\",\n",
    "            layer=LAYER.N,\n",
    "            energy=50,\n",
    "            ion=\"P\",\n",
    "            dose=1e12,\n",
    "            resist_thickness=1.0,\n",
    "        ),\n",
    "        gp.ImplantPhysical(\n",
    "            name=\"deep_p_implant\",\n",
    "            layer=LAYER.P,\n",
    "            energy=50,\n",
    "            ion=\"B\", # The type of ion being implanted is Boron (B), it has one less valence electron than silicon, this makes it a P-type dopant.\n",
    "            dose=1e12,\n",
    "            resist_thickness=1.0,\n",
    "        ),\n",
    "        gp.ImplantPhysical(\n",
    "            name=\"shallow_p_implant\",\n",
    "            layer=LAYER.P,\n",
    "            energy=15,\n",
    "            ion=\"B\",\n",
    "            dose=1e12,\n",
    "            resist_thickness=1.0,\n",
    "        ),\n",
    "        # \"Temperatures of ~1000C for not more than a few seconds\"\n",
    "        # Adjust to your process\n",
    "        # https://en.wikipedia.org/wiki/Rapid_thermal_processing\n",
    "        gp.Anneal(\n",
    "            name=\"dopant_activation\",\n",
    "            time=1,\n",
    "            temperature=1000,\n",
    "        ),\n",
    "    )\n",
    "\n",
    "# This code calls a function named get_process() to load a predefined semiconductor fabrication process.\n",
    "process = get_process()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51",
   "metadata": {},
   "source": [
    "These process dataclasses can then be used in physical simulator plugins."
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "tags,-all",
   "custom_cell_magics": "kql",
   "encoding": "# -*- coding: utf-8 -*-"
  },
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
